#include <string.h>

#include <windows.h>
#include <stdlib.h>
#include <time.h>

#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>

#include "lib.h"
#include "odbc.h"
#include "log.h"

// Required for release build!!
#include <malloc.h>

// SQLGetData is behaving differently under Visual Studio .NET 2003
// than it did under Visual Studio 6. Specifically, when trying to read a single
// byte value (uint8) it is fetching too much data.
//
// This new behavior agrees with the documentation in MSDN:
//
// When the driver returns fixed-length data, such as an integer or a date 
// structure, the driver ignores BufferLength and assumes the buffer is large 
// enough to hold the data. It is therefore important for the application to 
// allocate a large enough buffer for fixed-length data or the driver will 
// write past the end of the buffer. 
//

inline
SQLRETURN  
SafeSQLGetData(	SQLHSTMT StatementHandle,
				SQLUSMALLINT ColumnNumber, 
				SQLSMALLINT TargetType,
				SQLPOINTER TargetValue, 
				SQLLEN BufferLength,
				SQLLEN *StrLen_or_Ind )
{
	SQLRETURN result = SQL_SUCCESS;
	unsigned char* HeapBuffer = 0;
	unsigned char* Buffer = 0;

	if ( BufferLength < (sizeof(GUID)) )
	{
		// Small buffers are allocated on the stack
		Buffer = static_cast<unsigned char*>(_alloca(sizeof(GUID)));
	}
	else
	{
		// Large buffers are allocated on the heap
		HeapBuffer = new unsigned char[BufferLength];
		Buffer = HeapBuffer;
	}

	// Clear the buffer before calling SQLGetData (see FogBugz case 2602 for
	// details why we need to zero out a temporary buffer instead of the actual
	// target.  The short answer is that we want to have as small a window as
	// possible where the target buffer does not have accurate data because we
	// have some thread unsafe code.
	memset( Buffer, 0, BufferLength );

	result = SQLGetData( StatementHandle, ColumnNumber, TargetType, 
						 Buffer, BufferLength, StrLen_or_Ind);

	if ( SQL_SUCCEEDED(result) )
	{
		// Copy the data from the temporary buffer to the target
		memcpy( TargetValue, Buffer, BufferLength );
	}
	else
	{
		// On SQL error clear out the target buffer
		memset( TargetValue, 0, BufferLength );
	}

	delete [] HeapBuffer;

	return result;
}

#define SQLGetData SafeSQLGetData

enum ODBCErrors
{
	ODBC_OK = 1000, ODBC_MEMORY_ERROR, ODBC_EXEC_ERROR, ODBC_ENV_ERROR,
	ODBC_STMT_ERROR, ODBC_CONNECT_ERROR, ODBC_DBC_ERROR,
	ODBC_POOL_NOT_INITIALIZED, ODBC_GETDATA_ERROR, ODBC_BINDRESULT_ERROR,
	ODBC_BINDQUERY_ERROR, ODBC_FETCH_ERROR, ODBC_PREPARE_ERROR
};

ODBCPool::ODBCPool()
{
	hEnv = (uint32)SQL_NULL_HENV;
}

ODBCPool::~ODBCPool()
{
	if (hEnv)
	{
		SQLFreeHandle(SQL_HANDLE_ENV, (SQLHENV)hEnv);
		hEnv = (uint32)SQL_NULL_HENV;
	}
}

int32
ODBCPool::error(int32 reason)
{
	switch (reason)
	{
		case ODBC_MEMORY_ERROR:
			Log::msgError("ODBC Pool Error: Out of memory");
			break;
		case ODBC_ENV_ERROR:
			Log::msgError("ODBC Pool Error: Could not allocate environment");
			break;
		case ODBC_CONNECT_ERROR:
			Log::msgError("ODBC Pool Error: Could not connect to database");
			break;
		case ODBC_DBC_ERROR:
			Log::msgError("ODBC Pool Error: Could not allocate ODBC connection");
			break;
		case ODBC_POOL_NOT_INITIALIZED:
			Log::msgError("ODBC Pool Error: Connection pool not initialized");
			break;
	}

	uint16 state[16], msg[256];
	int16 len, i;
	int32 errcode=0;

	i = 1;
	while (SQL_SUCCESS == SQLGetDiagRec(SQL_HANDLE_ENV, (SQLHANDLE)hEnv,
		i++, state, &errcode, msg, 256, &len))
	{
		Log::msgError(TEXT("ODBCPool: SQLSTATE: %s. Error: (%d). Msg: %s."), state, errcode, msg);
	}

	return errcode;
}

bool8
ODBCPool::init()
{
	if (SQL_SUCCEEDED(SQLSetEnvAttr(NULL, SQL_ATTR_CONNECTION_POOLING,
			(SQLPOINTER)SQL_CP_ONE_PER_HENV, SQL_IS_INTEGER))
		&&
		SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,
			(SQLHENV*)&hEnv)))
	{
		SQLSetEnvAttr((SQLHENV)hEnv, SQL_ATTR_ODBC_VERSION,
			(SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);
		return true;
	}

	error(ODBC_ENV_ERROR);
	return false;
}

ODBCConnection*
ODBCPool::open(const String& dsn, const String& login, const String& password, int32 timeout)
{
	ODBCConnection* result = (ODBCConnection*)0;

	if (hEnv)
	{
		SQLRETURN retcode = -1;
		uint32 hDBc;

		if (SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_DBC,
			(SQLHENV)hEnv, (SQLHDBC*)&hDBc)))
		{
			SQLSetConnectAttr((SQLHDBC)hDBc, SQL_ATTR_LOGIN_TIMEOUT,
				(SQLPOINTER)timeout, SQL_IS_INTEGER);

			if (login && password && !login.isEmpty() && !password.isEmpty())
			{
				// SQL authentication
				retcode = SQLConnect((SQLHDBC)hDBc,
					(SQLWCHAR *)(const SQLWCHAR *)dsn,		SQL_NTS,
					(SQLWCHAR *)(const SQLWCHAR *)login,	SQL_NTS,
					(SQLWCHAR *)(const SQLWCHAR *)password,	SQL_NTS);
			}
			else if (dsn && !dsn.isEmpty())
			{
				// Windows authentication

				// Convert the DSN into an appropriate connection string
				// (Note that the lengths of the two static additions to the
				// connection string are 4 and 23)
				String connString;

				connString  = "DSN=";
				connString += dsn;
				connString += ";Trusted_Connection=YES";

				retcode = SQLDriverConnect((SQLHDBC)hDBc, (SQLHWND)NULL,
					(SQLWCHAR*)(const SQLWCHAR*)connString, SQL_NTS,
					NULL, 0, NULL,
					SQL_DRIVER_NOPROMPT);
			}

			if (SQL_SUCCEEDED(retcode))
			{
				result = new ODBCConnection(hDBc);
				if (!result)
				{
					error(ODBC_MEMORY_ERROR);
					SQLFreeHandle(SQL_HANDLE_DBC, (SQLHDBC)hDBc);
				}
			}
			else
			{
				error(ODBC_CONNECT_ERROR);

                // kharmon.  07-17-07.  Adding a little extra error reporting to see WHY
                // the connection attempt failed.
	            uint16 state[16], msg[256];
	            int16 len, i;
	            int32 errcode=0;

	            i = 1;
	            while (SQL_SUCCESS == SQLGetDiagRec(SQL_HANDLE_DBC, (SQLHANDLE)hDBc,
		            i++, state, &errcode, msg, 256, &len))
	            {
		            Log::msgError(TEXT("ODBCPool: SQLSTATE: %s. Error: (%d). Msg: %s."), state, errcode, msg);
	            }

				SQLFreeHandle(SQL_HANDLE_DBC, (SQLHDBC)hDBc);
			}
		}
		else
			error(ODBC_DBC_ERROR);
	}
	else
		error(ODBC_POOL_NOT_INITIALIZED);

	return result;
}

//
// ---------------------------------------------------------------------------
//

ODBCConnection::ODBCConnection(uint32 hdbc)
{
	hDBc = hdbc;
}

ODBCConnection::~ODBCConnection()
{
	if (hDBc)
	{
		SQLDisconnect((SQLHDBC)hDBc);
		SQLFreeHandle(SQL_HANDLE_DBC, (SQLHDBC)hDBc);
		hDBc = (uint32)SQL_NULL_HDBC;
	}
}

int32
ODBCConnection::error(int32 reason)
{
	switch (reason)
	{
		case ODBC_MEMORY_ERROR:
			Log::msgError("ODBC Connection Error: Out of memory");
			break;
		case ODBC_DBC_ERROR:
			Log::msgError("ODBC Connection Error: Could not allocate ODBC "
				"connection");
			break;
		case ODBC_STMT_ERROR:
			Log::msgError("ODBC Connection Error: Could not allocate statement");
			break;
	}

	uint16 state[16], msg[256];
	int16 len, i;
	int32 errcode=0;

	i = 1;
	while (SQL_SUCCESS == SQLGetDiagRec(SQL_HANDLE_DBC, (SQLHANDLE)hDBc,
		i++, state, &errcode, msg, 256, &len))
	{
		Log::msgError(TEXT("ODBCConnection: SQLSTATE: %s. Error: (%d). Msg: %s."), state, errcode, msg);
	}

	return errcode;
}

ODBCQuery*
ODBCConnection::query()
{
	ODBCQuery* result = (ODBCQuery*)0;

	if (hDBc)
	{
		SQLRETURN retcode;
		uint32 hstmt;

		retcode = SQLAllocHandle(SQL_HANDLE_STMT, (SQLHDBC)hDBc,
			(SQLHSTMT*)&hstmt);
		if (SQL_SUCCEEDED(retcode))
		{
			result = new ODBCQuery(hstmt);
			if (!result)
			{
				error(ODBC_MEMORY_ERROR);
				SQLFreeHandle(SQL_HANDLE_STMT, (SQLHSTMT)hstmt);
			}
		}
		else
			error(ODBC_STMT_ERROR);
	}
	else
		error(ODBC_DBC_ERROR);

	return result;
}

//
// ---------------------------------------------------------------------------
//

uint8 ODBCQuery::sHex[16] =
{
	'0', '1', '2', '3', '4', '5', '6', '7',
	'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
};

ODBCQuery::ODBCQuery(uint32 hstmt)
{
	hStmt = hstmt;
	needComma = false;
}

ODBCQuery::~ODBCQuery()
{
	if (hStmt)
	{
		SQLFreeHandle(SQL_HANDLE_STMT, (SQLHSTMT)hStmt);
		hStmt = (uint32)SQL_NULL_HSTMT;
	}
}

int32
ODBCQuery::error(int32 reason, bool8 suppressTruncationError)
{
	switch (reason)
	{
		case ODBC_MEMORY_ERROR:
			Log::msgError("ODBC Query Error: Out of memory");
			break;
		case ODBC_BINDQUERY_ERROR:
			Log::msgError("ODBC Query Error: bindQuery failed");
			break;
		case ODBC_EXEC_ERROR:
			Log::msgError("ODBC Query Error: Could not execute query: [%S]",
				(const uint16*)query);
			break;
		case ODBC_BINDRESULT_ERROR:
			Log::msgError("ODBC Query Error: bindResult failed");
			break;
		case ODBC_FETCH_ERROR:
			Log::msgError("ODBC Query Error: fetch failed");
			break;
		case ODBC_STMT_ERROR:
			Log::msgError("ODBC Query Error: statement reset failed");
			break;
		case ODBC_PREPARE_ERROR:
			Log::msgError("ODBC Query Error: statement reset failed");
			break;
		//
		// Don't add a case for ODBC_GETDATA_ERROR...or you'll generate
		// and log an error that's not really an error.  When retreiving
		// TEXT data or when retreiving data in chunks, all getData()
		// call succeed until the final one, which fails signaling the end.
		//
	}

	uint16 state[16], msg[256];
	int16 len, i;
	int32 errcode=0;

	i = 1;
	while (SQL_SUCCESS == SQLGetDiagRec(SQL_HANDLE_STMT, (SQLHANDLE)hStmt,
		i++, state, &errcode, msg, 256, &len))
	{
		// SQLSTATE: 01004 indicates data truncation
		if (!(suppressTruncationError && !wcscmp(state, TEXT("01004"))))
			Log::msgError("ODBCQuery: SQLSTATE: %S. Error: (%d). Msg: %S. Query: %S.", state, errcode, msg, (const uint16*)query); // CGC 11/11/2010 CSN-3065 
	}

	UDEBUGMSG((TEXT("QUERY: [%s]"), (const wchar_t*)query));

	return errcode;
}

bool8
ODBCQuery::reset()
{
	if (hStmt)
	{
		SQLRETURN retcode;

		retcode = SQLFreeStmt((SQLHSTMT)hStmt, SQL_UNBIND | SQL_RESET_PARAMS);
		if (SQL_SUCCEEDED(retcode))
		{
			if (retcode == SQL_SUCCESS_WITH_INFO)
				error(ODBC_OK);

			return true;
		}
		else
			error(ODBC_STMT_ERROR);
	}
	return false;
}

void
ODBCQuery::define(const String& query)
{
	needComma = false;

	this->query = query;
}

void
ODBCQuery::append(const String& toEndOfQuery)
{
	this->query += toEndOfQuery;
}

void
ODBCQuery::addParam(const String::Character& param, QuoteOption quotes)
{
	addComma();

	if (quotes)
		query += "'";

	query += param;

	if (quotes)
	{
		if (param == '\'')
			query += "'";

		query += "'";
	}
}

void
ODBCQuery::addParam(const String& param, int32 len, QuoteOption quotes)
{
	int32 numEmbedded = 0;
	int32 embedded = 0, section = 0;

	addComma();

	if (len == 0)
	{
		if (quotes)
		{
			query += "''";
		}

		return;
	}

	if (quotes)
	{
		embedded = param.find('\'');
		while (embedded > -1)
		{
			++numEmbedded;
			embedded = param.find('\'', embedded + 1);
		}
	}

	if (quotes)
		query += "N'";

	if (!numEmbedded)
	{
        query += param.substring( 0, len );
	}
	else
	{
		section = 0;
		embedded = param.find('\'', section);
		while (embedded > -1)
		{
			if (embedded - section + 1)
			{
				const uint16* data = (const uint16*)param + section;

                String sData(data);
                query += sData.substring( 0, embedded - section + 1 );
			}
			query += "'";

			section = embedded + 1;
			embedded = param.find('\'', section);
		}
		if (section > -1)
		{
			const uint16* data   = (const uint16*)param + section;
			const uint32  lenNew = (const uint32)(const uint16*)param + len - section;

            String sData(data);
            query += sData.substring( 0, lenNew );
		}
	}

	if (quotes)
		query += "'";
}

void
ODBCQuery::addParam(const String& param, QuoteOption quotes)
{
	addParam(param, param.getLength(), quotes);
}

void
ODBCQuery::addParam(uint8 param, BinaryOption binary)
{
	char tempQuery[32]; // More than enough for the binary representation of a uint8
	int32 used=0;

	memset(tempQuery, 0, 32);

	addComma();

	if (binary == BINARY)
	{
		tempQuery[used++] = '0';
		tempQuery[used++] = 'x';

		tempQuery[used++] = sHex[(param >> 4) & 0x0f];
		tempQuery[used++] = sHex[param & 0x0f];
	}
	else if (binary == HEXSTRING)
	{
		tempQuery[used++] = '\'';

		tempQuery[used++] = sHex[(param >> 4) & 0x0f];
		tempQuery[used++] = sHex[param & 0x0f];

		tempQuery[used++] = '\'';
	}
	else
	{
		int32 len, i, j;
		uint8 temp;
#			define SWAP(a,b)	{	temp = tempQuery[used + (a)]; \
								tempQuery[used + (a)] = tempQuery[used+(b)]; \
								tempQuery[used + (b)] = temp; }

		for (len = 0; param; len++)
		{
			tempQuery[used + len] = '0' + (param % 10);
			param /= 10;
		}

		if (len == 0)
			tempQuery[used + len++] = '0';

		// reverse it
		//
		for (i = 0, j = len - 1; i < j; i++, j--)
			SWAP(i, j);

		used += len;

#			undef SWAP
	}

	tempQuery[used] = '\0';

    query += tempQuery;
}

void
ODBCQuery::addParam(uint8* param, int32 len, BinaryOption binary)
{
	bool8 success = true;

	addComma();

	if (binary == BINARY)
	{
        query += "0";
        query += "x";

		while (len-- > 0)
		{
            query += String::Character((sHex[(*param >> 4) & 0x0f]));
			query += String::Character((sHex[*param++ & 0x0f]));
		}
	}
	else if (binary == HEXSTRING)
	{
		query += "'";

		while (len-- > 0)
		{
			query += String::Character((sHex[(*param >> 4) & 0x0f]));
			query += String::Character((sHex[*param++ & 0x0f]));
		}

		query += "'";
	}
	else
	{
		// this probably shouldn't be available...dangerous
		ASSERT(false);
	}

	if (!success)
	{
		Log::msgError("FAILED  ODBCQuery::addParam(uint8* <not printed>, %d, %d)",
			len, binary);
	}
}

void
ODBCQuery::addParam(int32 param, MoneyOption money)
{
	uint16 tempQuery[32]; // More than enough for a representation of an int32
	int32 used=0;
	bool8 isNeg;
	int32 len, i, j;
	uint16 temp;
#		define SWAP(a,b)	{	temp = tempQuery[used + (a)]; \
							tempQuery[used + (a)] = tempQuery[used+(b)]; \
							tempQuery[used + (b)] = temp; }

	memset(tempQuery, 0, 32);

	addComma();

	isNeg = param < 0;
	if (isNeg)
		param = -param;

	if (money == ODBCQuery::MONEY)		// param contains cents
	{
		len = 0;
		while (param)
		{
			tempQuery[used + len++] = '0' + (char)(param % 10);
				param /= 10;

			if (len == 2)
				tempQuery[used + len++] = '.';
		}

		while (len < 4)
			tempQuery[used + len++] = (char)(len != 2 ? '0' : '.');
	}
	else if(money == ODBCQuery::MILLIMONEY) //param contains hundredths of a cent
	{
		len = 0;
		while (param)
		{
			tempQuery[used + len++] = '0' + (char)(param % 10);
				param /= 10;

			if (len == 4)
				tempQuery[used + len++] = '.';
		}

		while (len < 5)
			tempQuery[used + len++] = (char)(len != 4 ? '0' : '.');
	}
	else
	{
		for (len = 0; param; len++)
		{
			tempQuery[used + len] = (uint16)('0' + (param % 10));
			param /= 10;
		}

		if (len == 0)
			tempQuery[used + len++] = '0';
	}

	if (isNeg)
		tempQuery[used + len++] = '-';

	tempQuery[used + len] = '\0';

	// reverse it
	//
	for (i = 0, j = len - 1; i < j; i++, j--)
		SWAP(i, j);

	used += len;

#	undef SWAP

    query += tempQuery;
}



void
ODBCQuery::addParam(int64 param, MoneyOption money)
{
	char tempQuery[64]; // More than enough for an int64
	int32 used=0;

	bool8 isNeg;
	int32 len, i, j;
	uint8 temp;
#		define SWAP(a,b)	{	temp = tempQuery[used + (a)]; \
							tempQuery[used + (a)] = tempQuery[used+(b)]; \
							tempQuery[used + (b)] = temp; }

	memset(tempQuery, 0, 64);

	addComma();

	isNeg = param < 0;
	if (isNeg)
		param = -param;

	if (money)		// param contains cents
	{
		len = 0;
		while (param)
		{
			tempQuery[used + len++] = '0' + (char)(param % 10);
			param /= 10;

			if (len == 2)
				tempQuery[used + len++] = '.';
		}

		while (len < 4)
			tempQuery[used + len++] = (char)(len != 2 ? '0' : '.');
	}
	else
	{
		for (len = 0; param; len++)
		{
			tempQuery[used + len] = '0' + (char)(param % 10);
			param /= 10;
		}

		if (len == 0)
			tempQuery[used + len++] = '0';
	}

	if (isNeg)
		tempQuery[used + len++] = '-';

	tempQuery[used + len] = '\0';

	// reverse it
	//
	for (i = 0, j = len - 1; i < j; i++, j--)
		SWAP(i, j);

	used += len;

#	undef SWAP

	query += tempQuery;
}



void
ODBCQuery::addParam(uint32 param, BinaryOption binary)
{
	char tempQuery[32]; // More than enough for a uint32
	int32 used=0;

	memset(tempQuery, 0, 32);

	addComma();

	if (binary == BINARY)
	{
		tempQuery[used++] = '0';
		tempQuery[used++] = 'x';

		tempQuery[used++] = sHex[(param >> 28) & 0x0f];
		tempQuery[used++] = sHex[(param >> 24) & 0x0f];
		tempQuery[used++] = sHex[(param >> 20) & 0x0f];
		tempQuery[used++] = sHex[(param >> 16) & 0x0f];
		tempQuery[used++] = sHex[(param >> 12) & 0x0f];
		tempQuery[used++] = sHex[(param >> 8) & 0x0f];
		tempQuery[used++] = sHex[(param >> 4) & 0x0f];
		tempQuery[used++] = sHex[param & 0x0f];
	}
	else if (binary == HEXSTRING)
	{
		tempQuery[used++] = '\'';

		tempQuery[used++] = sHex[(param >> 28) & 0x0f];
		tempQuery[used++] = sHex[(param >> 24) & 0x0f];
		tempQuery[used++] = sHex[(param >> 20) & 0x0f];
		tempQuery[used++] = sHex[(param >> 16) & 0x0f];
		tempQuery[used++] = sHex[(param >> 12) & 0x0f];
		tempQuery[used++] = sHex[(param >> 8) & 0x0f];
		tempQuery[used++] = sHex[(param >> 4) & 0x0f];
		tempQuery[used++] = sHex[param & 0x0f];

		tempQuery[used++] = '\'';
	}
	else
	{
		int32 len, i, j;
		uint8 temp;
#		define SWAP(a,b)	{	temp = tempQuery[used + (a)]; \
								tempQuery[used + (a)] = tempQuery[used+(b)]; \
								tempQuery[used + (b)] = temp; }

		for (len = 0; param; len++)
		{
			tempQuery[used + len] = (char)('0' + (param % 10));
			param /= 10;
		}

		if (len == 0)
			tempQuery[used + len++] = '0';

		tempQuery[used + len] = '\0';

		// reverse it
		//
		for (i = 0, j = len - 1; i < j; i++, j--)
			SWAP(i, j);

		used += len;

#		undef SWAP
	}

    query += tempQuery;
}




bool8
ODBCQuery::exec(const String& query, uint32 queryTimeout)
{
	bool8 result = false;
	uint8 numSubmits = 0;
	int32 randDelay=500;

	DEBUGMSG(("Query: [%S]", (const uint16*)query));

	srand( (unsigned)time( NULL ) );

	if (hStmt)
	{
		SQLRETURN retcode;
		int32 errcode;

		do
		{
			// Reset error code
			errcode=0;

			if (numSubmits > 0)
			{
				// Must cancel the query before re-submitting
				retcode = SQLCancel((SQLHSTMT)hStmt);
				if (SQL_SUCCEEDED(retcode))
				{
					Log::msgWarning(TEXT("Resubmitting deadlocked query after %dms: %s"), randDelay, (const uint16*)query);
				}
				else
				{
					Log::msgError(TEXT("Failed cancelling query - aborting: %s"), (const uint16*)query);
					break;
				}
			}

			SQLUINTEGER Timeout = queryTimeout;
			retcode = SQLSetStmtAttr( (SQLHSTMT)hStmt, SQL_ATTR_QUERY_TIMEOUT, (void*)Timeout, 0);
			if (SQL_SUCCEEDED(retcode))
			{
				if (retcode == SQL_SUCCESS_WITH_INFO)
					errcode=error(ODBC_OK);
			}
			else
				errcode=error(ODBC_EXEC_ERROR);

			retcode = SQLExecDirect((SQLHSTMT)hStmt, (SQLWCHAR*)(const SQLWCHAR*)query, SQL_NTS);

#if DEBUG


SQLWCHAR			Sqlstate[1024];
SQLINTEGER      NativeErrorPtr;
SQLWCHAR			MessageText[1024];
SQLSMALLINT     TextLengthPtr;
SQLGetDiagRec(		SQL_HANDLE_STMT,
					(SQLHSTMT)hStmt,
					(SQLSMALLINT)1,
					(SQLWCHAR *)&Sqlstate,
					(SQLINTEGER*)&NativeErrorPtr,
					(SQLWCHAR *) &MessageText,
					(SQLSMALLINT)1024,
					(SQLSMALLINT*)&TextLengthPtr);

#endif


			if (SQL_SUCCEEDED(retcode) || retcode == SQL_NO_DATA)
			{
				result = true;

				if (retcode == SQL_SUCCESS_WITH_INFO)
					errcode=error(ODBC_OK);
			}
			else
				errcode=error(ODBC_EXEC_ERROR);

			// On deadlock -- pause for 500ms-1000ms
			randDelay = (rand()%500)+500;
			if (errcode==1205)
				Sleep(randDelay);

			numSubmits++;
		}
		while ((errcode==1205) && (numSubmits < 3));
		// 1205 is a deadlock condition -- we need to resubmit
		// We will submit a maximum of 3 times

		if (numSubmits >= 3)
			Log::msgError(TEXT("Exceeded maximum deadlock resubmits: %s"), (const uint16*)query);
	}
	return result;
}

bool8
ODBCQuery::exec(uint32 queryTimeout)
{
	return exec(query, queryTimeout);
}

bool8
ODBCQuery::exec(const String& query)
{
	return exec(query, 0);
}

bool8
ODBCQuery::exec()
{
	return exec(query, 0);
}

int32
ODBCQuery::numCols()
{
	if (hStmt)
	{
		SQLSMALLINT num;
		SQLRETURN retcode;

		retcode = SQLNumResultCols((SQLHSTMT)hStmt, &num);
		if (SQL_SUCCEEDED(retcode))
		{
			if (retcode == SQL_SUCCESS_WITH_INFO)
				error(ODBC_OK);

			return (int32)num;
		}
	}

	return 0;
}

bool8
ODBCQuery::getRow()
{
	if (hStmt)
	{
		SQLRETURN retcode;

		do
		{
			retcode = SQLFetch((SQLHSTMT)hStmt);
			if (SQL_SUCCEEDED(retcode))
			{
				if (retcode == SQL_SUCCESS_WITH_INFO)
					error(ODBC_OK);

				return true;
			}

			retcode = SQLMoreResults((SQLHSTMT)hStmt);
		} while SQL_SUCCEEDED(retcode);
	}

	return false;
}

bool8
ODBCQuery::getData(uint16 column, void* data, int32 datamax, bool8* isNull)
{
	if (hStmt)
	{
		SQLRETURN retcode;
		SQLLEN copied;

		retcode = SQLGetData((SQLHSTMT)hStmt, column,
			SQL_C_DEFAULT, data, datamax, &copied);

		if (SQL_SUCCEEDED(retcode))
		{
			if (isNull)
				*isNull = (bool8)(copied == SQL_NULL_DATA);

			if (retcode == SQL_SUCCESS_WITH_INFO)
				error(ODBC_OK);

			return true;
		}
		else
			error(ODBC_GETDATA_ERROR);
	}
	return false;
}

bool8
ODBCQuery::getData(uint16 column, int64* data, int32 datamax, bool8* isNull)
{
	if (hStmt)
	{
		SQLRETURN retcode;
		SQLLEN copied;

		retcode = SQLGetData((SQLHSTMT)hStmt, column,
			SQL_C_SBIGINT, data, datamax, &copied);

		if (SQL_SUCCEEDED(retcode))
		{
			if (isNull)
				*isNull = (bool8)(copied == SQL_NULL_DATA);

			if (retcode == SQL_SUCCESS_WITH_INFO)
				error(ODBC_OK);

			return true;
		}
		else
			error(ODBC_GETDATA_ERROR);
	}
	return false;
}

bool8
ODBCQuery::getData(uint16 column, char* data, int32 maxLenIncludingNull,
	int32* numGotten, bool8* isNull, bool8 suppressTruncationError)
{
	if (hStmt)
	{
		SQLRETURN retcode;
		SQLLEN copied = SQL_NO_TOTAL;

		retcode = SQLGetData((SQLHSTMT)hStmt, column,
			SQL_C_CHAR, data, maxLenIncludingNull, &copied);

		if (SQL_SUCCEEDED(retcode) || (copied == SQL_NO_TOTAL))
		{
			if (numGotten)
				*numGotten = copied;

			if (isNull)
				*isNull = (bool8)(copied == SQL_NULL_DATA);

			if (copied == SQL_NULL_DATA)
				*data = '\0';

			if (retcode == SQL_SUCCESS_WITH_INFO)
				error(ODBC_OK, suppressTruncationError);

			return true;
		}
		else
			error(ODBC_GETDATA_ERROR, suppressTruncationError);
	}
	return false;
}


bool8
ODBCQuery::getData(uint16 column, String* data, bool8* isNull, bool8 suppressTruncationError)
{
	if (!data)
		return false;

	if (hStmt)
	{
		uint16 temp[2048] = { '\0' };
		SQLRETURN retcode;
		SQLLEN SQLNoTotal = SQL_NO_TOTAL;
		bool8 result = false;

        String strTemp;

		strTemp.empty();

		while ((retcode = SQLGetData((SQLHSTMT)hStmt, column, SQL_WCHAR, temp, sizeof(temp),
			&SQLNoTotal)) != SQL_NO_DATA && retcode != SQL_ERROR)
		{
			result = true;

			strTemp += temp;
		}

		if (SQLNoTotal == SQL_NULL_DATA)
		{
			strTemp.empty();

			if (isNull)
				*isNull = (bool8)(SQLNoTotal == SQL_NULL_DATA);
		}

        *data = strTemp;

		return result;
	}
	return false;
}

bool8
ODBCQuery::getData(uint16 column, String& data, bool8* isNull, bool8 suppressTruncationError)
{
	return getData(column, &data, isNull, suppressTruncationError);
}

bool8
ODBCQuery::getData(uint16 column, uint8* data, int32 maxLenIncludingNull,
	int32* numGotten, bool8* isNull, bool8 suppressTruncationError)
{
	if (hStmt)
	{
		SQLRETURN retcode;
		SQLLEN copied;

		retcode = SQLGetData((SQLHSTMT)hStmt, column,
			SQL_C_BINARY, data, maxLenIncludingNull, &copied);

		if (SQL_SUCCEEDED(retcode))
		{
			*numGotten = copied;

			if (isNull)
				*isNull = (bool8)(copied == SQL_NULL_DATA);

			if (retcode == SQL_SUCCESS_WITH_INFO)
				error(ODBC_OK, suppressTruncationError);

			return true;
		}
		else
			error(ODBC_GETDATA_ERROR, suppressTruncationError);
	}
	return false;
}

bool8
ODBCQuery::getData(uint16 column, int32* data, MoneyOption isMoney,
	bool8* isNull)
{
	if (hStmt)
	{
		if (isMoney == ODBCQuery::MONEY)
		{
			//Source is MONEY, but not more fine grained than 1 cent
			//If there are fractional cents, they will be rounded
			char buf[64];
			SQLLEN copied;
			SQLRETURN retcode;
			int32 val, i, isNeg, decSeen, decDigits;

			val = 0;

			retcode = SQLGetData((SQLHSTMT)hStmt, column, SQL_CHAR,
				&buf, 63, &copied);
			if (SQL_SUCCEEDED(retcode))
			{
				if (retcode == SQL_SUCCESS_WITH_INFO)
					error(ODBC_OK);

				if (isNull)
					*isNull = (bool8)(copied == SQL_NULL_DATA);

				i = decSeen = decDigits = 0;
				isNeg = buf[i] == '-';
				if (isNeg)
					++i;

				while (buf[i])
				{
					if (buf[i] != '.')
					{
						val = val * 10 + (buf[i] - '0');

						if (decSeen && ++decDigits == 2)
						{
							if (buf[i + 1] >= '5')	// round up
								++val;
							break;
						}
					}
					else
						decSeen = 1;

					++i;
				}

				*data = isNeg ? -val : val;
				return true;
			}
			else
            {
                *data = 0;
				error(ODBC_GETDATA_ERROR);
            }
		}
		else if (isMoney == ODBCQuery::MILLIMONEY)
		{
		//Source is MONEY, but and four decimals of accuracy are needed (*,4)
		//The result will be padded with zeros, and the result will be in thousands of a dollar

			char buf[64];
			SQLLEN copied;
			SQLRETURN retcode;
			int32 val, i, isNeg, decSeen, decDigits;

			val = 0;

			retcode = SQLGetData((SQLHSTMT)hStmt, column, SQL_CHAR,
				&buf, 63, &copied);
			if (SQL_SUCCEEDED(retcode))
			{
				if (retcode == SQL_SUCCESS_WITH_INFO)
					error(ODBC_OK);

				if (isNull)
					*isNull = (bool8)(copied == SQL_NULL_DATA);

				i = decSeen = decDigits = 0;
				isNeg = buf[i] == '-';
				if (isNeg)
					++i;

				while (buf[i])
				{
					if (buf[i] != '.')
					{
						val = val * 10 + (buf[i] - '0');

						if (decSeen && ++decDigits == 4)
						{
							if (buf[i + 1] >= '5')	// round up
								++val;
							break;
						}
					}
					else
						decSeen = 1;

					++i;
				}

				*data = isNeg ? -val : val;
				return true;
			}
			else
            {
                *data = 0;
				error(ODBC_GETDATA_ERROR);
            }
        }
		else
		{
			return getData(column, data, sizeof(int32), isNull);
		}
	}
	return false;
}

bool8
ODBCQuery::getData(uint16 column, uint32* data, bool8* isNull)
{
	if (hStmt)
		return getData(column, data, sizeof(uint32), isNull);
	return false;
}

bool8
ODBCQuery::getData(uint16 column, int64* data, bool8* isNull)
{
	if (hStmt)
		return getData(column, data, sizeof(int64), isNull);
	return false;
}


bool8
ODBCQuery::getData(uint16 column, int16* data, bool8* isNull)
{
	if (hStmt)
		return getData(column, data, sizeof(int16), isNull);
	return false;
}

bool8
ODBCQuery::getData(uint16 column, uint8* data, bool8* isNull)
{
	if (hStmt)
		return getData(column, data, sizeof(uint8), isNull);
	return false;
}

bool8
ODBCQuery::getData(uint16 column, int8* data, bool8* isNull)
{
	if (hStmt)
		return getData(column, data, sizeof(int8), isNull);
	return false;
}


void
ODBCQuery::addComma()
{
	if (needComma)
	{
		query += ",";
		query += " ";
	}
	needComma = true;
}

const String&
ODBCQuery::getQuery()
{
	return query;
}

int32
ODBCQuery::getLength()
{
	return query.getLength();
}
